package tp

import (
	"database/sql"
	"fmt"
	"sync"
)

import (
	"github.com/denisenkom/go-mssqldb"
)

import (
	dbc "dbConnector"
	"r_log"
	"resource"
	"util"
)

type TradingPosition struct {
	CaclTime     util.TimeStamp
	Date         util.DateAndTime
	Batch        [util.PERIOD_COUNT]int32
	Res          *resource.Resource
	RawDatasLock sync.RWMutex
	RawDatas     map[string]*TPRawDataOnEachServer
	PcLocks      [util.PERIOD_COUNT]sync.Mutex
	PointChanges [util.PERIOD_COUNT]*[]PointChange
	State        TpState
	StateLock    sync.RWMutex
}

type TpState struct {
	TpState bool
}

type PointChange struct {
	Flowid      uint64
	Userid      util.UserID //账户id
	Period      util.Period //周期
	Acc         *SubAccount
	CdOnly      bool       //是否只做超跌
	Amount      int64      //加仓数
	Capital     int64      //每仓加仓额
	Level       util.Level //账户级别
	MethodType  uint8
	MethodStyle util.MethodStyle
	ConvState   util.ConvType //账户收敛类型
	// Badluck bool       //是否账户收益较低
	State *AccState
	TPRD  *TPRawDataOnEachServer
}

type TPRawDataOnEachServer struct {
	ServerInfo   dbc.ServerInfo
	IsReady      bool
	AccStates    []*AccState
	PointChanges [util.PERIOD_COUNT]*[]PointChange
}

type AccState struct {
	UserId   util.UserID
	Period   util.Period
	Need     int64
	Assigned int64
	Priority float64 //默认值0
}

func NewTradingPosition(calcTime util.TimeStamp, date util.DateAndTime, batch [util.PERIOD_COUNT]int32, res *resource.Resource) *TradingPosition {
	tp := new(TradingPosition)
	tp.CaclTime = calcTime
	tp.Date = date
	tp.Batch = batch
	tp.Res = res
	tp.RawDatas = make(map[string]*TPRawDataOnEachServer)
	tp.PointChanges = [util.PERIOD_COUNT]*[]PointChange{nil, nil, nil, nil}
	return tp
}

func (tp *TradingPosition) Init() error {
	dbc.ServerLock.RLock()
	sl := make([]dbc.ServerInfo, 0, len(dbc.Servers.Account)) //数据库地址列表
	for _, si := range dbc.Servers.Account {
		sl = append(sl, *si)
	}
	dbc.ServerLock.RUnlock()

	//获取信息
	errorlist := make([]error, 0)
	var errorlock sync.Mutex
	var wg sync.WaitGroup
	for _, s := range sl {
		tp.RawDatasLock.Lock()
		rd, ok := tp.RawDatas[s.DataBase]
		if !ok {
			rd = new(TPRawDataOnEachServer)
			rd.IsReady = false
			rd.ServerInfo = s
			rd.AccStates = make([]*AccState, 0)
			rd.PointChanges = [util.PERIOD_COUNT]*[]PointChange{nil, nil, nil, nil}
			tp.RawDatas[s.DataBase] = rd
		} else {
			if !rd.IsReady {
				rd.AccStates = make([]*AccState, 0)
				rd.PointChanges = [util.PERIOD_COUNT]*[]PointChange{nil, nil, nil, nil}
			}
		}
		tp.RawDatasLock.Unlock()
		wg.Add(1)
		go func(server dbc.ServerInfo, rawDatas *TPRawDataOnEachServer) {
			defer wg.Done()
			if rawDatas.IsReady {
				return
			}
			db := server.DB
			/*			db, err := server.InitSqlServer()
						if err != nil {
							errorlock.Lock()
							errorlist = append(errorlist, fmt.Errorf("initSqlServer(%v): %v", server, err))
							errorlock.Unlock()
							return
						}*/

			err := tp.getTps(db, rawDatas, server.IsSAcc > 0)
			//err = tp.getTpsTest(db, rawDatas, server.IsSAcc > 0)
			if err != nil {
				r_log.Logger.Error(err)
				db.Close()
				errorlock.Lock()
				errorlist = append(errorlist, fmt.Errorf("tp.getTps(%v): %v", server, err))
				errorlock.Unlock()
				return
			}
			//			db.Close()
		}(s, rd)
	}
	wg.Wait()
	//	r_log.Logger.Info("TPS FINISH")
	tp.StateLock.Lock()
	defer tp.StateLock.Unlock()
	if len(errorlist) != 0 {
		tp.State.TpState = util.STATE_ERROR
		return fmt.Errorf("Get err when get tps! %v", errorlist)
	} else {
		tp.State.TpState = util.STATE_READY
		return nil
	}
}

func (tp *TradingPosition) getTps(db *sql.DB, rd *TPRawDataOnEachServer, issacc bool) error {
	rows, err := db.Query(`Exec P_RecommenderServer_LoadUserInfoAndTPS ?,?,?,?,?,?`, tp.CaclTime, tp.Date, tp.Batch[0], tp.Batch[1], tp.Batch[2], tp.Batch[3])
	if err != nil {
		return fmt.Errorf("db.Query(): %v", err)
	}
	defer rows.Close()
	tp.Res.SpLock.RLock()
	sps, ok := tp.Res.StockPools[rd.ServerInfo.DataBase]
	if !ok {
		tp.Res.SpLock.RUnlock()
		return fmt.Errorf("Not found stockpools on server %v!", rd.ServerInfo.DataBase)
	}
	tp.Res.SpLock.RUnlock()

	flowid := new(uint64)
	personid := new(util.UserID)
	klineType := new(util.Period)
	cdModel := new(uint8)
	accReduceType := new(uint8)
	accLevel := new(uint8)
	userConfig := new(uint8) //最低位：0不接受调配，1接受调配；次低位：0不使用确认池评级，1使用评级
	methodtype := new(uint8)
	need := new(int64)
	assigned := new(int64)
	priority := new(float64)
	cdOnly := new(uint8)
	amount := new(int64)
	capital := new(float64)
	level := new(util.Level)
	badluck := new(uint8)
	methodStyle := new(uint8)
	convState := new(uint8) //账户收敛类型
	CYBTag := new(int16)
	KCBTag := new(int16)

	subaccmap := make(map[util.AccountID]*SubAccount)
	accmap := make(map[util.UserID]*Account)
	for rows.Next() {
		if err = rows.Scan(flowid, personid, klineType, cdModel, accReduceType, accLevel, userConfig, methodtype, need, assigned, priority, cdOnly, amount, capital, level, badluck, convState, methodStyle, CYBTag, KCBTag); err != nil {
			r_log.Logger.Error("tp.getTps(): %v", fmt.Errorf("rows.Scan(): %v", err))
			continue
		}

		if *methodStyle == 0 || *convState == 0 {
			r_log.Logger.Error("tp.getTps(): %v", fmt.Errorf("未知收敛状态或打法风格"))
			continue
		}

		l, err := util.ConvertLevel_TPS(*level)
		if err != nil {
			r_log.TPSLogger.Info("PC(period = %v): unknown level value(level = %v): uerid = %v ", klineType, *level, *personid)
			r_log.Logger.Warn("ConvertLevel(): %v", err)
			continue
		}

		pacc, ok := accmap[*personid]
		if !ok {
			pacc = newAccount(*personid)
			pacc.Version = util.ACCVERSION(*accLevel - 1)
			pacc.IsSAcc = issacc
			accmap[*personid] = pacc
		}

		follow := new(resource.StockPool)
		follows, ok := sps.Stockpools[*personid]
		if !ok {
			r_log.Logger.Info("Not found user's(%v) stockpools!", *personid)
			follow = nil
		} else {
			follow = (*follows)[*klineType]
		}

		subacc := &SubAccount{
			ParentAcc:   pacc,
			UserId:      *personid,
			Period:      *klineType,
			AcceptRecom: *userConfig&uint8(1) > 0,
			UsePoolLvl:  *userConfig&uint8(2) > 0,
			OneYearLoss: *userConfig&uint8(4) > 0,
			Style:       *accReduceType,
			CdModel:     *cdModel > 0,
			Holdings:    make(map[util.StkID]*HodingInfo),
			Follows:     follow,
			CYBTag:      *CYBTag,
			KCBTag:      *KCBTag}

		accState := &AccState{
			UserId:   *personid,
			Period:   *klineType,
			Need:     *need,
			Assigned: *assigned,
			Priority: *priority}

		pointchange := PointChange{
			Flowid:      *flowid,
			Userid:      *personid,
			Period:      *klineType,
			Acc:         subacc,
			CdOnly:      *cdOnly == 1,
			Amount:      *amount,
			Capital:     int64(*capital * 100),
			Level:       l,
			MethodType:  *methodtype,
			MethodStyle: util.MethodStyle(*methodStyle) - 1,
			ConvState:   util.ConvType(*convState) - 1,
			//			Badluck: *badluck == 1,
			State: accState,
			TPRD:  rd}

		if rd.PointChanges[*klineType] == nil {
			rd.PointChanges[*klineType] = &[]PointChange{pointchange}
		} else {
			*rd.PointChanges[*klineType] = append(*rd.PointChanges[*klineType], pointchange)
		}
		accid := util.GeneratorSubAccID(*personid, *klineType)
		subaccmap[accid] = subacc

		rd.AccStates = append(rd.AccStates, accState)
	}
	if err = rows.Err(); err != nil {
		r_log.Logger.Warn("tp.getTps(%v):%v", db, fmt.Errorf("rows.Err(): %v", rows.Err()))
	}
	//	r_log.Logger.Info("GetHolding Start %v", db)
	if err = tp.getHoldings(db, &subaccmap, &accmap); err != nil {
		return fmt.Errorf("GetHoldings(): %v", err)
	}
	//	r_log.Logger.Info("GetHolding End %v", db)
	for p := 0; p < util.PERIOD_COUNT; p++ {
		tp.PcLocks[p].Lock()
		if tp.PointChanges[p] == nil {
			tp.PointChanges[p] = new([]PointChange)
		}
		if rd.PointChanges[p] != nil {
			*tp.PointChanges[p] = append(*tp.PointChanges[p], *rd.PointChanges[p]...)
		}
		tp.PcLocks[p].Unlock()
	}

	return nil
}

//测试
func (tp *TradingPosition) getTpsTest(db *sql.DB, rd *TPRawDataOnEachServer, issacc bool) error {
	tp.Res.SpLock.RLock()
	sps, ok := tp.Res.StockPools[rd.ServerInfo.DataBase]
	if !ok {
		tp.Res.SpLock.RUnlock()
		return fmt.Errorf("Not found stockpools on server %v!", rd.ServerInfo.DataBase)
	}
	tp.Res.SpLock.RUnlock()

	flowid := new(uint64)
	personid := new(util.UserID)
	klineType := new(util.Period)
	cdModel := new(uint8)
	accReduceType := new(uint8)
	accLevel := new(uint8)
	userConfig := new(uint8) //最低位：0不接受调配，1接受调配；次低位：0不使用确认池评级，1使用评级
	need := new(int64)
	assigned := new(int64)
	priority := new(float64)
	cdOnly := new(uint8)
	amount := new(int64)
	capital := new(float64)
	level := new(util.Level)
	badluck := new(uint8)
	methodStyle := new(uint8)
	convState := new(uint8) //账户收敛类型

	subaccmap := make(map[util.AccountID]*SubAccount)
	accmap := make(map[util.UserID]*Account)
	for i := 0; i < 36; i++ {
		*flowid = 1
		*personid = 111 + util.UserID(i)
		*klineType = 0
		*cdModel = 1
		*accReduceType = 1 + uint8(i)/18
		*accLevel = 0
		*userConfig = 1 //最低位：0不接受调配，1接受调配；次低位：0不使用确认池评级，1使用评级
		*need = 0
		*assigned = 0
		*priority = 0
		*cdOnly = 0
		*amount = 1 + int64(i)%18/9
		*capital = 50000
		*level = 1
		*badluck = 0
		*methodStyle = 1 + uint8(i%18%9%3)
		*convState = 1 + uint8(i%18%9/3) //账户收敛类型

		l, err := util.ConvertLevel(*level)
		if err != nil {
			r_log.TPSLogger.Info("PC(period = %v): unknown level value(level = %v): uerid = %v ", klineType, *level, *personid)
			r_log.Logger.Warn("ConvertLevel(): %v", err)
			//		continue
		}

		pacc, ok := accmap[*personid]
		if !ok {
			pacc = newAccount(*personid)
			pacc.Version = util.ACCVERSION(*accLevel - 1)
			pacc.IsSAcc = issacc
			accmap[*personid] = pacc
		}

		follow := new(resource.StockPool)
		follows, ok := sps.Stockpools[*personid]
		if !ok {
			r_log.Logger.Info("Not found user's(%v) stockpools!", *personid)
			follow = nil
		} else {
			follow = (*follows)[*klineType]
		}

		subacc := &SubAccount{
			ParentAcc:   pacc,
			UserId:      *personid,
			Period:      *klineType,
			AcceptRecom: *userConfig&uint8(1) > 0,
			UsePoolLvl:  *userConfig&uint8(2) > 0,
			Style:       *accReduceType,
			CdModel:     *cdModel > 0,
			Holdings:    make(map[util.StkID]*HodingInfo),
			Follows:     follow}

		accState := &AccState{
			UserId:   *personid,
			Period:   *klineType,
			Need:     *need,
			Assigned: *assigned,
			Priority: *priority}

		pointchange := PointChange{
			Flowid:      *flowid,
			Userid:      *personid,
			Period:      *klineType,
			Acc:         subacc,
			CdOnly:      *cdOnly == 1,
			Amount:      *amount,
			Capital:     int64(*capital * 100),
			Level:       l,
			MethodStyle: util.MethodStyle(*methodStyle) - 1,
			ConvState:   util.ConvType(*convState) - 1,
			//			Badluck: *badluck == 1,
			State: accState,
			TPRD:  rd}

		if rd.PointChanges[*klineType] == nil {
			rd.PointChanges[*klineType] = &[]PointChange{pointchange}
		} else {
			*rd.PointChanges[*klineType] = append(*rd.PointChanges[*klineType], pointchange)
		}
		accid := util.GeneratorSubAccID(*personid, *klineType)
		subaccmap[accid] = subacc

		rd.AccStates = append(rd.AccStates, accState)
	}
	//	r_log.Logger.Info("GetHolding Start %v", db)
	if err := tp.getHoldingsTest(db, &subaccmap, &accmap); err != nil {
		return fmt.Errorf("GetHoldings(): %v", err)
	}
	//	r_log.Logger.Info("GetHolding End %v", db)
	for p := 0; p < util.PERIOD_COUNT; p++ {
		tp.PcLocks[p].Lock()
		if tp.PointChanges[p] == nil {
			tp.PointChanges[p] = new([]PointChange)
		}
		if rd.PointChanges[p] != nil {
			*tp.PointChanges[p] = append(*tp.PointChanges[p], *rd.PointChanges[p]...)
		}
		tp.PcLocks[p].Unlock()
	}

	return nil
}

func (tp *TradingPosition) getHoldingsTest(db *sql.DB, subaccmap *map[util.AccountID]*SubAccount, accmap *map[util.UserID]*Account) error {

	//	r_log.Logger.Info("GetHolding Process2 %v", db)
	userid := new(util.UserID)
	period := new(util.Period)
	stkid := new(util.StkID)
	marketSector := new(util.MarketCode)
	var lastId util.AccountID = 0
	var subacc *SubAccount = nil
	var ok bool
	firstRecord := true
	//	r_log.Logger.Info("GetHolding Process3 %v", db)
	//		r_log.Logger.Info("GetHolding Process3.1 %v", db)
	*userid = 111
	*period = 0
	*stkid = 0
	*marketSector = 0

	accid := util.GeneratorSubAccID(*userid, *period)

	//r_log.Logger.Info("GetHolding Process3.2 %v", db)
	if firstRecord || lastId != accid {
		firstRecord = false
		lastId = accid
		if subacc, ok = (*subaccmap)[accid]; !ok {
			subacc = nil
		}
	}

	//		r_log.Logger.Info("GetHolding Process3.3 %v", db)
	var pacc *Account
	if subacc != nil {
		pacc = subacc.ParentAcc
		_, ok = subacc.Holdings[*stkid]
		if ok {
			r_log.Logger.Warn("getHoldings(): %v", fmt.Errorf("Get duplicate stock %v in subAccount userid=%v, period=%v!", *stkid, *userid, *period))
		}
		subacc.Holdings[*stkid] = &HodingInfo{
			Stkid:        *stkid,
			MarketSector: *marketSector}
	} else {
		if pacc, ok = (*accmap)[*userid]; !ok {
			return fmt.Errorf("Miss user's(userid=%v, period=%v) parent account!", *userid, *period)
		}
	}

	//		r_log.Logger.Info("GetHolding Process3.4 %v", db)
	holdingCount, ok := pacc.Holdings[*stkid]
	if !ok {
		pacc.Holdings[*stkid] = 1
	} else {
		pacc.Holdings[*stkid] = holdingCount + 1
	}

	//	r_log.Logger.Info("GetHolding Process4 %v", db)

	return nil
}

func (tp *TradingPosition) getHoldings(db *sql.DB, subaccmap *map[util.AccountID]*SubAccount, accmap *map[util.UserID]*Account) error {
	rows, err := db.Query("Exec P_RecommenderServer_LoadStkList ?,?,?,?,?,?", tp.CaclTime, tp.Date, tp.Batch[0], tp.Batch[1], tp.Batch[2], tp.Batch[3])
	//	r_log.Logger.Info("GetHolding Process1 %v", db)
	if err != nil {
		return fmt.Errorf("db.Query(): %v", err)
	}
	defer rows.Close()

	//	r_log.Logger.Info("GetHolding Process2 %v", db)
	userid := new(util.UserID)
	period := new(util.Period)
	stkid := new(util.StkID)
	marketSector := new(util.MarketCode)
	var lastId util.AccountID = 0
	var subacc *SubAccount = nil
	var ok bool
	firstRecord := true
	//	r_log.Logger.Info("GetHolding Process3 %v", db)
	for rows.Next() {
		//		r_log.Logger.Info("GetHolding Process3.1 %v", db)
		if err = rows.Scan(userid, period, stkid, marketSector); err != nil {
			r_log.Logger.Error("getHoldings(): %v", fmt.Errorf("rows.Scan(): %v", err))
			continue
		}
		accid := util.GeneratorSubAccID(*userid, *period)

		//r_log.Logger.Info("GetHolding Process3.2 %v", db)
		if firstRecord || lastId != accid {
			firstRecord = false
			lastId = accid
			if subacc, ok = (*subaccmap)[accid]; !ok {
				subacc = nil
			}
		}

		//		r_log.Logger.Info("GetHolding Process3.3 %v", db)
		var pacc *Account
		if subacc != nil {
			pacc = subacc.ParentAcc
			_, ok = subacc.Holdings[*stkid]
			if ok {
				r_log.Logger.Warn("getHoldings(): %v", fmt.Errorf("Get duplicate stock %v in subAccount userid=%v, period=%v!", *stkid, *userid, *period))
			}
			subacc.Holdings[*stkid] = &HodingInfo{
				Stkid:        *stkid,
				MarketSector: *marketSector}
		} else {
			if pacc, ok = (*accmap)[*userid]; !ok {
				return fmt.Errorf("Miss user's(userid=%v, period=%v) parent account!", *userid, *period)
			}
		}

		//		r_log.Logger.Info("GetHolding Process3.4 %v", db)
		holdingCount, ok := pacc.Holdings[*stkid]
		if !ok {
			pacc.Holdings[*stkid] = 1
		} else {
			pacc.Holdings[*stkid] = holdingCount + 1
		}
	}
	//	r_log.Logger.Info("GetHolding Process4 %v", db)
	if err = rows.Err(); err != nil {
		r_log.Logger.Warn("tp.getHoldings(%v):%v", db, fmt.Errorf("rows.Err(): %v", rows.Err()))
	}
	return nil
}

//更新优先级
func (pc *PointChange) UpdatePriority(need, assigned int64) {
	pc.State.Need += need
	pc.State.Assigned += assigned
	if pc.State.Need == 0 {
		pc.State.Priority = 0
	} else {
		pc.State.Priority = float64(pc.State.Need-pc.State.Assigned) / float64(pc.State.Need)
	}
}

func (tp *TradingPosition) SaveAccState() {
	var wg sync.WaitGroup
	for _, s := range dbc.Servers.Account {
		wg.Add(1)
		go func(server dbc.ServerInfo) {
			defer wg.Done()
			db := server.DB
			/*			db, err := server.InitSqlServer()
						if err != nil {
							r_log.Logger.Error("tp.SaveAccState(): %v", fmt.Errorf("initSqlServer(%v): %v", server, err))
							return
						}*/
			tp.RawDatasLock.RLock()
			rawData, _ := tp.RawDatas[s.DataBase]
			tp.RawDatasLock.RUnlock()
			err := saveAccState(db, &rawData.AccStates)
			if err != nil {
				db.Close()
				r_log.Logger.Error("tp.SaveAccState(): %v", fmt.Errorf("saveAccState(%v): %v", server, err))
				return
			}
			//			db.Close()
		}(*s)
	}
	wg.Wait()
}

func saveAccState(db *sql.DB, accstates *[]*AccState) error {
	var result []AccState
	for i := 0; i < len(*accstates); i++ {
		result = append(result, *(*accstates)[i])
	}
	if len(result) == 0 {
		return nil
	}
	tvpType := mssql.TVPType{
		TVPTypeName: "AccStateType",
		TVPScheme:   "dbo",
		TVPValue:    result,
	}
	_, err := db.Exec("exec P_RecommenderServer_SaveAccState ?", tvpType)
	if err != nil {
		return err
	}
	return nil
}
